﻿using System;
using System.Text.RegularExpressions;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Utility class with common validation methods.
    /// </summary>
    public static class Validator
    {
        #region RegularExpressions.

        /// <summary>Contains commonly used regular expressions.</summary>
        public static class RegularExpressions
        {
            /// <summary>Valid e-mail addres format..</summary>
            public static readonly Regex Email = new Regex(@"^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$", RegexOptions.Compiled);

            ///// <summary>Valid HTTP, HTTPS, FTP or FTPS URL format.</summary>
            //public static readonly Regex Url = new Regex(@"^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$", RegexOptions.Compiled);

            /// <summary>Between 6 and 8 digits.</summary>
            public static Regex SixToEightDigits = new Regex("^[0-9]{6-8}$", RegexOptions.Compiled);

            /// <summary>Exactly 8 digits.</summary>
            public static Regex EightDigits = new Regex("^[0-9]{8}$", RegexOptions.Compiled);

            /// <summary>Exactly 9 digits.</summary>
            public static Regex NineDigits = new Regex("^[0-9]{9}$", RegexOptions.Compiled);

            /// <summary>Exactly 11 digits.</summary>
            public static Regex ElevenDigits = new Regex("^[0-9]{11}$", RegexOptions.Compiled);

            /// <summary>Exactly 13 digits.</summary>
            public static Regex ThirteenDigits = new Regex("^[0-9]{13}$", RegexOptions.Compiled);
        }

        #endregion

        #region CheckString.

        /// <summary>
        /// Checks if the given string obeys the specified format.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="minLength">Minimum length of the text. <b>null</b> is treated as invalid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckString(string val, int minLength)
        {
            if (val != null && val.Length >= minLength)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Checks if the given string obeys the specified format.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="minLength">Minimum length of the text. <b>null</b> is treated as invalid value.</param>
        /// <param name="maxLength">Maximum length of the text.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckString(string val, int minLength, int maxLength)
        {
            if (val != null && val.Length >= minLength && val.Length <= maxLength)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Checks if the given string obeys the specified format.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="regularExpression">Specifies valid text format/pattern.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckString(string val, bool isNullable, Regex regularExpression)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (regularExpression != null && regularExpression.IsMatch(val) == false)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given string obeys the specified format.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="acceptEmptyString">Specifies whether <see cref="String.Empty"/> represents a valid value.</param>
        /// <param name="minLength">Minimum length of the text. <b>null</b> is treated as invalid value.</param>
        /// <param name="maxLength">Maximum length of the text.</param>
        /// <param name="regularExpression">Specifies valid text format/pattern.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckString(string val, bool isNullable, bool acceptEmptyString, int minLength, int maxLength, Regex regularExpression)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (!acceptEmptyString && val == string.Empty)
                return false;

            if (val.Length < minLength)
                return false;

            if (maxLength >= 0 && val.Length > maxLength)
                return false;

            if (regularExpression != null && regularExpression.IsMatch(val) == false)
                return false;

            return true;
        }

        #endregion

        #region CheckInteger.

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">An integer value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckInteger(int? val, bool isNullable)
        {
            if (!isNullable && val == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">An integer value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckInteger(int? val, bool isNullable, int minimumValue)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (val.Value < minimumValue)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">An integer value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckInteger(int? val, bool isNullable, int minimumValue, int maximumValue)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (val.Value < minimumValue)
                return false;

            if (val.Value > maximumValue)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">An integer value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckInteger(int val, int minimumValue)
        {
            if (val < minimumValue)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">An integer value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckInteger(int val, int minimumValue, int maximumValue)
        {
            if (val < minimumValue)
                return false;

            if (val > maximumValue)
                return false;

            return true;
        }

        #endregion

        #region CheckRealNumber - double.

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(double? val, bool isNullable)
        {
            if (!isNullable && val == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="mustBePositive">Specifies whether only positive numbers represent a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(double? val, bool isNullable, bool mustBePositive)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (mustBePositive && val < 0)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(double? val, bool isNullable, int minimumValue, int maximumValue)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (val.Value < minimumValue)
                return false;

            if (val.Value > maximumValue)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="mustBePositive">Specifies whether only positive numbers represent a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(double val, bool mustBePositive)
        {
            if (mustBePositive && val < 0)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(double val, double minimumValue, double maximumValue)
        {
            if (val < minimumValue)
                return false;

            if (val > maximumValue)
                return false;

            return true;
        }

        #endregion

        #region CheckRealNumber - decimal.

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(decimal? val, bool isNullable)
        {
            if (!isNullable && val == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="mustBePositive">Specifies whether only positive numbers represent a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(decimal? val, bool isNullable, bool mustBePositive)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (mustBePositive && val < 0)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(decimal? val, bool isNullable, int minimumValue, int maximumValue)
        {
            if (val == null)
            {
                if (isNullable)
                    return true;
                else
                    return false;
            }

            if (val.Value < minimumValue)
                return false;

            if (val.Value > maximumValue)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="mustBePositive">Specifies whether only positive numbers represent a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(decimal val, bool mustBePositive)
        {
            if (mustBePositive && val < 0)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given number obeys the specified constraints.
        /// </summary>
        /// <param name="val">A floating point value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckRealNumber(decimal val, decimal minimumValue, decimal maximumValue)
        {
            if (val < minimumValue)
                return false;

            if (val > maximumValue)
                return false;

            return true;
        }

        #endregion

        #region CheckDate.

        /// <summary>
        /// Checks if the given DateTime value obeys the specified constraints.
        /// </summary>
        /// <param name="val">A date value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckDate(DateTime val, DateTime minimumValue)
        {
            if (val < minimumValue)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Checks if the given DateTime value obeys the specified constraints.
        /// </summary>
        /// <param name="val">A date value to validate.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckDate(DateTime val, DateTime minimumValue, DateTime maximumValue)
        {
            if (val < minimumValue)
                return false;

            if (val > maximumValue)
                return false;

            return true;
        }

        /// <summary>
        /// Checks if the given DateTime value obeys the specified constraints.
        /// </summary>
        /// <param name="val">A date value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckDate(DateTime? val, bool isNullable)
        {
            if (val == null && isNullable)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Checks if the given DateTime value obeys the specified constraints.
        /// </summary>
        /// <param name="val">A date value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckDate(DateTime? val, bool isNullable, DateTime minimumValue)
        {
            if (val == null)
            {
                return isNullable;
            }
            else
            {
                if (val.Value >= minimumValue)
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Checks if the given DateTime value obeys the specified constraints.
        /// </summary>
        /// <param name="val">A date value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <param name="minimumValue">Minimum allowed value.</param>
        /// <param name="maximumValue">Maximum allowed value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckDate(DateTime? val, bool isNullable, DateTime minimumValue, DateTime maximumValue)
        {
            if (val == null)
            {
                return isNullable;
            }
            else
            {
                if (val < minimumValue)
                    return false;

                if (val > maximumValue)
                    return false;

                return true;
            }
        }

        #endregion

        #region E-mail, OIB, JMBG, MB.

        /// <summary>
        /// Checks if the given string obeys e-mail address format.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckEmail(string val, bool isNullable)
        {
            return CheckString(val, isNullable, RegularExpressions.Email);
        }

        /// <summary>
        /// Checks if the given string obeys format for Croatian OIB - eleven digits. TODO: add control digit validation.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckOib(string val, bool isNullable)
        {
            bool isRegexMatch = CheckString(val, isNullable, RegularExpressions.ElevenDigits);
            if (isRegexMatch && (val != null))
                return CheckOibControlDigit(val);

            return isRegexMatch;
        }

        private static bool CheckOibControlDigit(string oib)
        {
            // ISO 7064 standard MOD 11,10.
            // Kontrolna znamenka prema navedenoj normi dobiva se slijedećim postupkom :
            // 1. prva znamenka zbroji se s brojem 10
            // 2. dobiveni zbroj cjelobrojno (s ostatkom) podijeli se brojem 10; ako je dobiveni
            // ostatak 0 zamijeni se brojem 10 (ovaj broj je tzv. međuostatak)
            // 3. dobiveni međuostatak pomnoži se brojem 2
            // 4. dobiveni umnožak cjelobrojno (s ostatkom) podijeli se brojem 11; ovaj ostatak
            // matematički nikako ne može biti 0 jer je rezultat prethodnog koraka uvijek paran
            // broj
            // 5. slijedeća znamenka zbroji se s ostatkom u prethodnom koraku
            // 6. ponavljaju se koraci 2, 3, 4 i 5  dok se ne potroše sve znamenke
            // 7. razlika između broja 11 i ostatka u zadnjem koraku je kontrolna znamenka

            if (oib.Length != 11)
                return false;

            long b;
            if (!long.TryParse(oib, out b))
                return false;

            int a = 10;
            for (int i = 0; i < 10; i++)
            {
                a = a + Convert.ToInt32(oib.Substring(i, 1));
                a = a % 10;
                if (a == 0) a = 10;
                a *= 2;
                a = a % 11;
            }

            int controlDigit = 11 - a;
            if (controlDigit == 10)
                controlDigit = 0;

            return controlDigit == Convert.ToInt32(oib.Substring(10, 1));
        }

        /// <summary>
        /// Checks if the given string obeys format for Croatian JMBG - 13 digits. TODO: add control digit validation.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckJmbg(string val, bool isNullable)
        {
            return CheckString(val, isNullable, RegularExpressions.ThirteenDigits);
        }

        /// <summary>
        /// Checks if the given string obeys format for Croatian company MB, ie. 8 digits. TODO: add control digit validation.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckMb(string val, bool isNullable)
        {
            return CheckString(val, isNullable, RegularExpressions.EightDigits);
        }

        /// <summary>
        /// Checks if the given string obeys format for Croatian company MBS, ie. 9 digits. TODO: add control digit validation.
        /// </summary>
        /// <param name="val">A textual value to validate.</param>
        /// <param name="isNullable">Specifies whether <b>null</b> represents a valid value.</param>
        /// <returns><b>true</b> is the given value is valid; <b>false</b> otherwise.</returns>
        public static bool CheckMbs(string val, bool isNullable)
        {
            // "Polje {0} nije obavezno, ali ako se upiše tada mora biti u ispravnom formatu."
            // "Polje {0} nije u ispravnom formatu.
            return CheckString(val, isNullable, RegularExpressions.NineDigits);
        }

        #endregion
    }
}